/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"
#include "../../common/UAC/vsfusb_UAC.h"
#include "component/av/tool/usb/vsfav_usb.h"

struct vsfusbh_uac_t
{
	struct vsfusbh_t *usbh;
	struct vsfusbh_device_t *dev;
	struct vsfusbh_ifs_t *ctrl_ifs;

	struct vsfhcd_urb_t *notify_urb;

	uint8_t ctrl_ifs_no;

	// av_dev MUST be the last member for variable size
	struct vsfav_dev_t av_dev;
};

static void vsfusbh_uac_free_urb(struct vsfusbh_t *usbh, struct vsfusbh_uac_t *uac)
{
	if (uac->notify_urb != NULL)vsfusbh_free_urb(usbh, &uac->notify_urb);
}

static vsf_err_t vsfusbh_uac_parse_desc(struct vsfav_usb_unit_parser_t *parser,
		struct vsfav_unit_t *unit, struct uavc_unit_descriptor_header *ud)
{
	union
	{
		struct vsfav_unit_feature_t *feature;
		struct vsfav_unit_mixer_t *mixer;
		struct vsfav_unit_input_stream_t *input_stream;
		struct vsfav_unit_output_stream_t *output_stream;
		struct vsfav_unit_selector_t *selector;
		struct vsfav_unit_processing_t *processing;
	} p;

	switch (ud->header.bDescriptorSubType)
	{
	case UAC_INPUT_TERMINAL:
	{
		struct uac_input_terminal_descriptor *id = (struct uac_input_terminal_descriptor *)ud;
		if (unit != NULL)
		{
			unit->id = id->bTerminalID;
			unit->type = VSFAV_INPUT;
		}
		if ((id->wTerminalType == UAC_UT_STREAMING) &&
			(p.input_stream = (struct vsfav_unit_input_stream_t *)vsfav_usb_getbuf(parser, sizeof(*p.input_stream))))
		{
			unit->type = VSFAV_STREAM_INPUT;
			unit->input_stream = p.input_stream;
		}
		parser->unit.input_number = 0;
		break;
	}
	case UAC_OUTPUT_TERMINAL:
	{
		struct uac_output_terminal_descriptor *od = (struct uac_output_terminal_descriptor *)ud;
		if (unit != NULL)
		{
			unit->id = od->bTerminalID;
			unit->type = VSFAV_OUTPUT;
		}
		if ((od->wTerminalType == UAC_UT_STREAMING) &&
			(p.output_stream = (struct vsfav_unit_output_stream_t *)vsfav_usb_getbuf(parser, sizeof(*p.output_stream))))
		{
			unit->type = VSFAV_STREAM_OUTPUT;
			unit->output_stream = p.output_stream;
		}
		parser->unit.input_number = 1;
		parser->unit.input_IDs = &od->bSourceID;
		break;
	}
	case UAC_MIXER_UNIT:
	{
		struct uac_mixer_unit_descriptor_h *muh = (struct uac_mixer_unit_descriptor_h *)ud;
//		struct uac_mixer_unit_descriptor_t *mut = (struct uac_mixer_unit_descriptor_t *)
//				((uint8_t *)muh + muh->bNrInPins);
		if (unit != NULL)
		{
			unit->id = muh->bUnitID;
			unit->type = VSFAV_MIXER;
		}
		if (p.mixer = (struct vsfav_unit_mixer_t *)vsfav_usb_getbuf(parser, sizeof(*p.mixer)))
		{
			unit->mixer = p.mixer;
		}
		parser->unit.input_number = muh->bNrInPins;
		parser->unit.input_IDs = muh->baSourceID;
		break;
	}
	case UAC_SELECTOR_UNIT:
	{
		struct uac_selector_unit_descriptor *su = (struct uac_selector_unit_descriptor *)ud;
		if (unit != NULL)
		{
			unit->id = su->bUnitID;
			unit->type = VSFAV_MIXER;
		}
		if (p.selector = (struct vsfav_unit_selector_t *)vsfav_usb_getbuf(parser, sizeof(*p.selector)))
		{
			unit->selector = p.selector;
		}
		parser->unit.input_number = su->bNrInPins;
		parser->unit.input_IDs = su->baSourceID;
		break;
	}
	case UAC_FEATURE_UNIT:
	{
		struct uac_feature_unit_descriptor *fu = (struct uac_feature_unit_descriptor *)ud;
		if (unit != NULL)
		{
			unit->id = fu->bUnitID;
			unit->type = VSFAV_FEATURE;
		}
		if (p.feature = (struct vsfav_unit_feature_t *)vsfav_usb_getbuf(parser, sizeof(*p.feature)))
		{
			unit->feature = p.feature;
		}
		parser->unit.input_number = 1;
		parser->unit.input_IDs = &fu->bSourceID;
		break;
	}
	case UAC1_PROCESSING_UNIT:
	{
		struct uac_processing_unit_descriptor_h *puh = (struct uac_processing_unit_descriptor_h *)ud;
		if (unit != NULL)
		{
			unit->id = puh->bUnitID;
			unit->type = VSFAV_PROCESSING;
		}
		if (p.processing = (struct vsfav_unit_processing_t *)vsfav_usb_getbuf(parser, sizeof(*p.processing)))
		{
			unit->processing = p.processing;
		}
		parser->unit.input_number = puh->bNrInPins;
		parser->unit.input_IDs = puh->baSourceID;
		break;
	}
	case UAC1_EXTENSION_UNIT:
		// extension unit need specified driver
	default:
		return VSFERR_FAIL;
	}
	return VSFERR_NONE;
}

static void *vsfusbh_uac_probe(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, struct vsfusbh_ifs_t *ifs,
		const struct vsfusbh_device_id_t *id)
{
	struct vsfusbh_ifs_alt_t *alt = &ifs->alt[ifs->cur_alt];
	struct vsfav_usb_unit_parser_t parser =
	{
		.op.parse_desc = vsfusbh_uac_parse_desc,
		.ifs_desc = alt->ifs_desc,
		.desc_len = alt->desc_size,
	};
	struct vsfusbh_uac_t *uac = NULL;

	// generate topology
	do
	{
		vsfav_usb_parser_init(&parser);
		if (vsfav_usb_generate_topology(&parser))
			goto free_all;

		if (!parser.buf && parser.size)
		{
			uint32_t punits_array_size = parser.unit_number * sizeof(struct vsfav_unit_t *);
			parser.size += sizeof(struct vsfusbh_uac_t) + punits_array_size;
			uac = vsf_bufmgr_malloc(parser.size);
			if (uac == NULL) return NULL;
			memset(uac, 0, sizeof(*uac));
			parser.av_dev = &uac->av_dev;
			parser.buf = (uint8_t *)&uac[1] + punits_array_size;
			parser.size = 0;
		}
	} while (!parser.size);

	// create streams
/*	for (i = 0; i < uac_header->bInCollection; i++)
	{
		data_ifs_no = uac_header->baInterfaceNr[i];
		if (data_ifs_no >= dev->config.num_of_ifs)
			goto free_all;
		data_ifs = &dev->config.ifs[data_ifs_no];

		if ((vsfusbh_get_extra_descriptor((uint8_t *)ifs_desc, alt->desc_size,
				USB_DT_CS_INTERFACE, (void **)&uac_general) != VSFERR_NONE) ||
			(uac_general->bDescriptorSubType != USB_UAC_SUBTYPE_AS_GENERAL))
		{
			goto free_all;
		}
	}
*/
	uac->notify_urb = vsfusbh_alloc_urb(usbh);
	if (!uac->notify_urb) goto free_all;
	uac->notify_urb->hcddev = &dev->hcddev;

	uac->ctrl_ifs = ifs;
	uac->ctrl_ifs_no = parser.ifs_desc->bInterfaceNumber;
	uac->usbh = usbh;
	uac->dev = dev;
/*
	uac->sm.init_state.evt_handler = vsfusbh_uac_evt_handler_init;
	uac->sm.user_data = uac;
	vsfsm_init(&uac->sm);
*/
	return uac;
free_all:
//	vsfusbh_uac_free(usbh, uac);
	if (uac != NULL)
		vsf_bufmgr_free(uac);
	return NULL;
}

static void vsfusbh_uac_disconnect(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, void *priv)
{
	struct vsfusbh_uac_t *uac = priv;
	if (uac == NULL) return;

	vsfusbh_uac_free_urb(usbh, uac);
//	vsfsm_fini(&uac->sm);
	vsf_bufmgr_free(uac);
}

static const struct vsfusbh_device_id_t vsfusbh_uac_id_table[] =
{
	{
		.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
			USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL,
		.bInterfaceClass = USB_CLASS_AUDIO,
		.bInterfaceSubClass = 0x01,		// AUDIOCONTROL
		.bInterfaceProtocol = 0X00,		// Undefined
	},
	{0},
};

const struct vsfusbh_class_drv_t vsfusbh_uac_drv =
{
	.name = "uac",
	.id_table = vsfusbh_uac_id_table,
	.probe = vsfusbh_uac_probe,
	.disconnect = vsfusbh_uac_disconnect,
};

